﻿/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using OptixDotNet;
using OptixDotNet.Math;
using OptixDotNet.Utils;

using Optix = OptixDotNet;

namespace OptixDotNetUtils
{
    public class OptixOBJLoader : OBJLoader
    {
        public string IntersecitonProgPath { get; set; }
        public string IntersecitonProgName { get; set; }
        public string BoundingBoxProgPath { get; set; }
        public string BoundingBoxProgName { get; set; }

        public Context Context { get; set; }
        public Material Material { get; set; }
        public GeometryGroup GeoGroup { get; set; }

        public AccelBuilder Builder { get; set; }
        public AccelTraverser Traverser { get; set; }

        public OptixOBJLoader( string filename, Context context, GeometryGroup geoGroup, Material material )
            : base( filename )
        {
            Context = context;
            GeoGroup = geoGroup;
            Material = material;

            Builder = AccelBuilder.Sbvh;
            Traverser = AccelTraverser.Bvh;
        }

        protected override void CreateGeometry()
        {
            base.CreateGeometry();

            if ( !GenerateGeometry )
                return;

            //create buffer descriptions
            BufferDesc vDesc = new BufferDesc() { Width = (uint)Positions.Count, Format = Format.Float3, Type = BufferType.Input };
            BufferDesc nDesc = new BufferDesc() { Width = (uint)Normals.Count, Format = Format.Float3, Type = BufferType.Input };
            BufferDesc tcDesc = new BufferDesc() { Width = (uint)Texcoords.Count, Format = Format.Float2, Type = BufferType.Input };

            // Create the buffers to hold our geometry data
            Optix.Buffer vBuffer = new Optix.Buffer( Context, vDesc );
            Optix.Buffer nBuffer = new Optix.Buffer( Context, nDesc );
            Optix.Buffer tcBuffer = new Optix.Buffer( Context, tcDesc );

            vBuffer.SetData<Vector3>( Positions.ToArray() );
            nBuffer.SetData<Vector3>( Normals.ToArray() );
            tcBuffer.SetData<Vector2>( Texcoords.ToArray() );

            List<GeometryInstance> instances = new List<GeometryInstance>();
            foreach ( ObjGroup group in Groups )
            {
                //empty group
                if ( group.VIndices.Count == 0 && group.NIndices.Count == 0 && group.TIndices.Count == 0 )
                    continue;

                ValidateGroup( group );

                bool normalsUseVIndices = GenerateNormals && group.NIndices.Count == 0 && Normals.Count > 0;

                if ( normalsUseVIndices )
                    System.Diagnostics.Debug.Assert( Normals.Count == Positions.Count );

                int numNormIndices = normalsUseVIndices ? group.VIndices.Count : group.NIndices.Count;

                BufferDesc viDesc = new BufferDesc() { Width = (uint)group.VIndices.Count, Format = Format.Int3, Type = BufferType.Input };
                BufferDesc niDesc = new BufferDesc() { Width = (uint)numNormIndices, Format = Format.Int3, Type = BufferType.Input };
                BufferDesc tiDesc = new BufferDesc() { Width = (uint)group.TIndices.Count, Format = Format.Int3, Type = BufferType.Input };

                Optix.Buffer viBuffer = new Optix.Buffer( Context, viDesc );
                Optix.Buffer niBuffer = new Optix.Buffer( Context, niDesc );
                Optix.Buffer tiBuffer = new Optix.Buffer( Context, tiDesc );

                viBuffer.SetData<Int3>( group.VIndices.ToArray() );
                //if normals weren't in the obj and we genereated them, use the vertex indices
                niBuffer.SetData<Int3>( normalsUseVIndices ? group.VIndices.ToArray() : group.NIndices.ToArray() );
                tiBuffer.SetData<Int3>( group.TIndices.ToArray() );

                //create a geometry node and set the buffers
                Geometry geometry = new Geometry( Context );
                geometry.IntersectionProgram = new Program( Context, IntersecitonProgPath, IntersecitonProgName );
                geometry.BoundingBoxProgram = new Program( Context, BoundingBoxProgPath, BoundingBoxProgName );
                geometry.PrimitiveCount = (uint)group.VIndices.Count;

                geometry[ "vertex_buffer" ].Set( vBuffer );
                geometry[ "normal_buffer" ].Set( nBuffer );
                geometry[ "texcoord_buffer" ].Set( tcBuffer );
                geometry[ "vindex_buffer" ].Set( viBuffer );
                geometry[ "nindex_buffer" ].Set( niBuffer );
                geometry[ "tindex_buffer" ].Set( tiBuffer );

                //create a geometry instance
                GeometryInstance instance = new GeometryInstance( Context );
                instance.Geometry = geometry;
                instance.AddMaterial( Material );

                if ( group.mtrl != null )
                {
                    ObjMaterial mtrl = mMtrls[ group.mtrl ];
                    instance[ "diffuse_color" ].SetFloat3( mtrl.Kd );
                    instance[ "emission_color" ].SetFloat3( mtrl.Ke );
                }
                else
                {
                    instance[ "diffuse_color" ].Set( 1.0f, 1.0f, 1.0f );
                }

                instances.Add( instance );
            }

            //create an acceleration structure for the geometry
            Acceleration accel = new Acceleration( Context, Builder, Traverser );

            if ( Builder == AccelBuilder.Sbvh || Builder == AccelBuilder.TriangleKdTree )
            {
                accel.VertexBufferName = "vertex_buffer";
                accel.IndexBufferName = "vindex_buffer";
            }

            //now attach the instance and accel to the geometry group
            GeoGroup.Acceleration = accel;
            GeoGroup.AddChildren( instances );
        }
    }
}
